package agent;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.omg.PortableInterceptor.INACTIVE;

import core.LocalVacuumEnvironmentPercept;
import core.VacuumEnvironment.LocationState;
import instanceXMLParser.Instance;
import instanceXMLParser.Position;
import aima.core.agent.Action;
import aima.core.agent.AgentProgram;
import aima.core.agent.Percept;
import aima.core.agent.impl.AbstractAgent;
import aima.core.agent.impl.DynamicAction;
import aima.core.agent.impl.NoOpAction;

public class VacuumAgent1 extends AbstractAgent implements AgentProgram {

	List<AbstractVacuumSolver> solvers = new ArrayList<>();

	volatile int passo = 0;

	volatile Map<String, List<Action>> solutions = new HashMap<>();

	volatile List<Action> finalSolution;

	int limitKBEST = 100;

	Instance instance;

	List<Thread> threads = new ArrayList<>();

	public VacuumAgent1() {
		solvers.add(new Vacuum_Agent_Solver());

		for (int i = 1; i < limitKBEST; i++) {
			solvers.add(new VacuumAgentSolverHeuristicKBest(i));
		}
	}

	public VacuumAgent1(Instance ist) {
		this.instance = ist;
		solvers.add(new Vacuum_Agent_Solver());

		for (int i = 1; i < limitKBEST; i++) {
			solvers.add(new VacuumAgentSolverHeuristicKBest(i));
		}

	}

	@Override
	public Action execute(Percept p) {
		final LocalVacuumEnvironmentPercept vep;
		try {
			if (passo == 0) {
				vep = (LocalVacuumEnvironmentPercept) p;
				creaInstance(vep);

				if (vep.getN() < 30) {
					Vacuum_Agent_Solver perfectSolver = (Vacuum_Agent_Solver) solvers
							.get(0);
					perfectSolver.instance = instance;
					Thread t = new Thread(perfectSolver);
					t.start();

					t.join();

					int NUMtargetCells = perfectSolver.targetCells.size();
					limitKBEST = calcBEST_KBEST(NUMtargetCells);

					solutions.put(((Vacuum_Agent_Solver) solvers.get(0))
							.getId(), ((Vacuum_Agent_Solver) solvers.get(0))
							.getSolution());
				}
				if (solutions.get("perfect") != null) {
					finalSolution = solutions.get("perfect");
				} else {
//					//System.out.println("CALCOLO EURISTICHE");
					// calcola euristiche

					if (vep.getN() < 30)
						for (int i = 1; i < limitKBEST; i++) {
							VacuumAgentSolverHeuristicKBest solverI = (VacuumAgentSolverHeuristicKBest) solvers
									.get(i);
							solverI.instance = instance;
							Thread thread = new Thread(solverI);
							threads.add(thread);
							thread.start();

						}

					solvers.add(new GAGreedy(vep));
					Thread threadGA = new Thread((GAGreedy) solvers.get(solvers
							.size() - 1));
					threads.add(threadGA);
					threadGA.start();

					if (vep.getN() < 30)
						for (int i = 1; i < limitKBEST; i++) {

							threads.get(i - 1).join();// il primo è il perfect
														// che
														// non gira in questi
														// threads
							solutions.put(
									((VacuumAgentSolverHeuristicKBest) solvers
											.get(i)).getId(),
									((VacuumAgentSolverHeuristicKBest) solvers
											.get(i)).getSolution());
//							//System.out.println("mess solutione " + i);
						}

					threadGA.join();
					//System.out.println("ga start");
					solutions.put(((GAGreedy) solvers.get(solvers.size() - 1))
							.getId(),
							((GAGreedy) solvers.get(solvers.size() - 1))
									.getSolution());
					//System.out.println("mess GAGreedy");

					// computa i kbest
					// finalSolution = solutions.get("kbest1");
					finalSolution = bestSolution();

				}
			}

		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		passo++;
		return finalSolution.get(passo - 1);
	}

	private int calcBEST_KBEST(int nUMtargetCells) {

		long LIMITCOMBINAZIONS = 1000;
		int count = 1;

		long combinations = binomial_coefficient(nUMtargetCells, count);

		if (combinations == 0)
			return 1;

		while (combinations > 0 && combinations < LIMITCOMBINAZIONS) {
			combinations = binomial_coefficient(nUMtargetCells, count);
			if (combinations > 0 && combinations < LIMITCOMBINAZIONS)
				count++;
		}

//		//System.err.println("LIMIT = " + count);
		return count;

	}

	static long binomial_coefficient(int n, int k) {

		long coeff = 1;
		for (int i = n - k + 1; i <= n; i++) {
			coeff *= i;
		}
		for (int i = 1; i <= k; i++) {
			coeff /= i;
		}
		return coeff;

	}

	private void creaInstance(LocalVacuumEnvironmentPercept p) {
		instance = new Instance();
		instance.setEnergy(p.getInitialEnergy());
		instance.setAgentPos(new Position((int) p.getAgentLocation().getX(),
				(int) p.getAgentLocation().getY()));
		instance.setBasePos(new Position((int) p.getBaseLocation().getX(),
				(int) p.getBaseLocation().getY()));
		instance.setSize(p.getN());
		Set<Action> keys = p.getActionEnergyCosts().keySet();
		List<Action> keysList = new ArrayList<>();
		keysList.addAll(keys);
		for (int i = 0; i < keysList.size(); i++) {
			String s = ((DynamicAction) keysList.get(i)).getName();
			instance.getActionCosts().put(s,
					p.getActionEnergyCosts().get(keysList.get(i)));
		}
		LocationState[][] states = instance.getBoardState();
		states = new LocationState[instance.getSize()][instance.getSize()];
		for (int i = 0; i < instance.getSize(); i++) {
			for (int j = 0; j < instance.getSize(); j++) {
				states[i][j] = p.getState().get(new Point(i, j));

			}
		}
		instance.setBoardState(states);
	}

	// rivedi quando
	private List<Action> bestSolution() {
		List<String> keys = new ArrayList<>();
		keys.addAll(solutions.keySet());
//		System.out.println(keys);
		String bestId = "";
		double bestPM = Double.MIN_VALUE;
		for (String id : keys) {

			if (!id.equals("perfect")) {

				
				int numSucked = 0;
				//-1 perch� il noop non va considerato
				int moviments = -1;
				for (Action act : solutions.get(id)) {
					if (((DynamicAction) act).getName().equals("suck")) {
						numSucked++;
					}
					else
					{
						moviments++;
					}
				}
				
				final double ET = instance.getEnergy()-moviments-numSucked*instance.getActionCosts().get("suck");
				final double BdT = 0;
				final double E0 = instance.getEnergy();
				final double CT = numSucked;
				
				double numDirty = 0;
				
				for (int i = 0; i < instance.getSize(); i++) {
					for (int j = 0; j < instance.getSize(); j++) {
						if(instance.getBoardState()[i][j]==LocationState.Dirty)
							numDirty++;
					}
				}
				
				final double D0 = numDirty;

				final Double performanceMeasure = Math.ceil((ET - BdT + 1) / (E0 + 1))
						* (CT + 1) / (D0 + 1) * (1 + (ET + 1) / (E0 + 1));

//				System.out.println(id);
//				System.out.println(" ET " +ET+ " Bdt "+BdT +" E0 "+E0+ " CT "+CT+ " D0 "+ D0 + " --> Performance "+ performanceMeasure);

//				final int performanceMeasure = numSucked;
		
//				System.err.println(performanceMeasure);
				
//				System.err.println("Performance measure " + id + ": "
//						+ performanceMeasure);
				if (performanceMeasure > bestPM) {
//					System.out.println(bestId);
					bestId = id;
					bestPM = performanceMeasure;
				}
			}

		}

//		System.err.println("Migliore e' il " + bestId);
		return solutions.get(bestId);
	}

}
